package com.googlecode.connectlet.crypto.cipher.rc5;

import com.googlecode.connectlet.crypto.cipher.Decipheror;
import com.googlecode.connectlet.crypto.cipher.Encipheror;
import com.googlecode.connectlet.util.Bytes;

public class RC532Cipher implements Encipheror, Decipheror {
	private static final long serialVersionUID = 1L;

	public static final int DEFAULT_ROUNDS = 16;
	public static final int BLOCK_SIZE = 8;

	private static final int RC5_P32 = 0xb7e15163;
	private static final int RC5_Q32 = 0x9e3779b9;

	private int[] s;

	public RC532Cipher(byte[] key) {
		this(DEFAULT_ROUNDS, key);
	}

	public RC532Cipher(int round, byte[] key) {
		s = new int[round * 2 + 2];
		s[0] = RC5_P32;
		for (int i = 1; i < s.length; i ++) {
			s[i] = s[i - 1] + RC5_Q32;
		}

		int[] l = new int[key.length / 4];
		for (int i = 0; i < l.length; i ++) {
			l[i] = Bytes.toInt(key, i * 4, Bytes.LITTLE_ENDIAN);
		}

		int a = 0, b = 0;
		int steps = 3 * Math.max(l.length, s.length);
		for (int i = 0; i < steps; i ++) {
			a = s[i % s.length] = Integer.rotateLeft(s[i % s.length] + a + b, 3);
			b = l[i % l.length] = Integer.rotateLeft(l[i % l.length] + a + b, (a + b) & 0x1f);
		}
	}

	@Override
	public int getBlockSize() {
		return BLOCK_SIZE;
	}

	@Override
	public void encrypt(byte[] src, int srcPos, byte[] dest, int destPos) {
		int a = Bytes.toInt(src, srcPos, Bytes.LITTLE_ENDIAN);
		int b = Bytes.toInt(src, srcPos + 4, Bytes.LITTLE_ENDIAN);
		a += s[0];
		b += s[1];
		for (int i = 2; i < s.length; i += 2) {
			a ^= b;
			a = Integer.rotateLeft(a, b & 0x1f);
			a += s[i];
			b ^= a;
			b = Integer.rotateLeft(b, a & 0x1f);
			b += s[i + 1];
		}
		Bytes.setInt(a, dest, destPos, Bytes.LITTLE_ENDIAN);
		Bytes.setInt(b, dest, destPos + 4, Bytes.LITTLE_ENDIAN);
	}

	@Override
	public void decrypt(byte[] src, int srcPos, byte[] dest, int destPos) {
		int a = Bytes.toInt(src, srcPos, Bytes.LITTLE_ENDIAN);
		int b = Bytes.toInt(src, srcPos + 4, Bytes.LITTLE_ENDIAN);
		for (int i = s.length - 2; i >= 2; i -= 2) {
			b -= s[i + 1];
			b = Integer.rotateRight(b, a & 0x1f);
			b ^= a;
			a -= s[i];
			a = Integer.rotateRight(a, b & 0x1f);
			a ^= b;
		}
		b -= s[1];
		a -= s[0];
		Bytes.setInt(a, dest, destPos, Bytes.LITTLE_ENDIAN);
		Bytes.setInt(b, dest, destPos + 4, Bytes.LITTLE_ENDIAN);
	}
}